<?php
/**
 * Created by PhpStorm.
 * Author: QinShuai
 * Email: qinshuai0423@qq.com
 * Date: 2021/6/17
 */

namespace backend\behaviors;

use common\components\AgentService;
use yii\base\Behavior;
use yii\base\Controller;
use Yii;

class AdminLogBehavior extends Behavior
{
    /**
     * @var array 配置信息
     */
    protected static $config = [
        /**
         *  单表最大纪录值，大于该值会自动分表
         */
        'max_rows' => 2000000,
        /**
         * 不开启日志记录的控制器，设置后该控制器下所有方法都将不记录日志
         */
        'not_record_controller' => [
            'admin-log'
        ],
        /**
         * 不记录日单的方法，例如 AdminGroup/add
         */
        'not_record_map' => [],
        /**
         * 操作日志存储表，不含表名前缀
         */
        'web_log_table' => 'admin_log',
        /**
         * data字段存储最大数据长度，0不受限制，避免因文章等数据导致字段超长，数据存储被截断而报错
         */
        'max_data_length' => 200,
        /**
         * 表名前缀，如果为null则为框架配置文件所设表前缀
         */
        'table_prefix' => null,
    ];

    /**
     * @var array 要写入到数据库的数据
     */
    protected static $param = [];

    /**
     * @var bool 是否禁用当前日志记录，禁用后不记录当前日志
     */
    protected static $forbid = false;
    /**
     * @return array|string[]
     */
    public function events()
    {
        return [
            Controller::EVENT_BEFORE_ACTION => "beforeAction",
            Controller::EVENT_AFTER_ACTION => 'afterAction'
        ];
    }

    /**
     * @param $event
     */
    public function beforeAction($event)
    {
        $this->init();
    }

    /**
     * @param $event
     * @throws \yii\db\Exception
     */
    public function afterAction($event)
    {
        $this->record($event);
    }

    /**
     * 获取日志记录信息
     *
     * @param $name
     *
     * @return mixed|null
     */
    public static function get($name)
    {
        return isset(self::$param[$name]) ? self::$param[$name] : null;
    }

    /**
     * 修改日志记录信息
     *
     * @param $name
     * @param $value
     */
    public static function set($name, $value)
    {
        if (isset(self::$param[$name])) {
            self::$param[$name] = $value;
        }
    }

    /**
     * 组装日志数据
     */
    public function init()
    {
        self::$param = [
            'uid' => Yii::$app->user->id,
            'method' => Yii::$app->request->method,
            'url' => Yii::$app->request->absoluteUrl,
            'module' => Yii::$app->id,
            'controller' => Yii::$app->controller->id,
            'action' => Yii::$app->controller->action->id,
            'data' => Yii::$app->request->method == 'GET' ? Yii::$app->request->get() : Yii::$app->request->post(),
            'ip' => Yii::$app->request->userIP,
            'location' => Yii::$app->request->userHost ?? '',
            'os' => AgentService::getOs(),
            'browser' => AgentService::getBroswer(),
            'user_agent' => Yii::$app->request->userAgent,
            'create_at' => time(),
        ];
    }

    /**
     * 日志记录入库
     * @param $event
     * @return bool
     * @throws \yii\db\Exception
     */
    public function record($event)
    {

        // 禁用记录日志时不记录
        if (self::$forbid) {
            return true;
        }
        // 不记录已排除的日志
        if (
            in_array(Yii::$app->controller->id, self::$config['not_record_controller'])
            || in_array(Yii::$app->controller->id . '/' . Yii::$app->controller->action->id, self::$config['not_record_map'])
        ) {
            return true;
        }
        Yii::$app->services->log->log([self::$param], '', 'admin_log');
        // 截取一部分数据，避免数据太大导致存储出错，比如文章发布提交的数据
        if (self::$config['max_data_length'] > 0) {
            foreach (self::$param['data'] as &$v) {
                if (is_string($v)) {
                    $v = mb_substr($v, 0, self::$config['max_data_length']);
                }
            }
        }
        self::$param['data'] = serialize(self::$param['data']);

        // 写入日志
        $tablePrefix = null === self::$config['table_prefix'] ? Yii::$app->db->tablePrefix : self::$config['table_prefix'];
        $table = $tablePrefix . self::$config['web_log_table'] . '_all';
        Yii::$app->db->createCommand()->insert($table, self::$param)->execute();
        $logId = Yii::$app->db->getLastInsertID();
        // 自动分表
        if ($logId % self::$config['max_rows'] == 0) {

            // 获取建表语句
            $result = Yii::$app->db->createCommand("SHOW CREATE TABLE {$table}")->queryAll();
            $sql = array_pop($result[0]);
            // 获取联合表的所有表名
            preg_match('/UNION=\(([^\)]*)/', $sql, $matches);
            $tables = explode(',', $matches[1]);
            // 取到最后一个表名，作为取 id 的依据
            $tableLast = end($tables);
            $tableLast = trim($tableLast, '`');
            // 表名都是包含零填充的三位整数
            $id = intval(substr($tableLast, -3, 3));
            $tableNew = $tablePrefix . self::$config['web_log_table'] . '_' . sprintf('%03d', $id + 1);
            // 建表并给设置自动递增 id
            self::createTable($tableNew, $tableLast, $id * intval(self::$config['max_rows']) + 1);
            // 更新 merge 表的 union 信息
            array_push($tables, $tableNew);
            Yii::$app->db->createCommand("ALTER TABLE {$table} UNION = (" . implode(',', $tables) . ")")->execute();
        }

        return true;
    }

    /**
     * 创建日志分表
     *
     * @param string $tableNew 新表表名
     * @param string $tableOld 参照表表名
     * @param int $autoIncrement 自增id
     * @return int
     * @throws Yii\db\Exception
     */
    private function createTable(string $tableNew, string $tableOld, $autoIncrement = 1)
    {
        // 获取建表语句
        $result = Yii::$app->db->createCommand("SHOW CREATE TABLE {$tableOld}")->queryAll();
        $sql = array_pop($result[0]);
        $sql = preg_replace(
            ['/CREATE TABLE `(\w+)`/', '/AUTO_INCREMENT=(\d+)/'],
            ["CREATE TABLE `{$tableNew}`", "AUTO_INCREMENT={$autoIncrement}"],
            $sql
        );

        return Yii::$app->db->createCommand($sql)->execute();
    }
}